package com.gitee.wsl.model.process

import com.gitee.wsl.model.process.call.body
import com.gitee.wsl.model.process.request.ProcessRequestBuilder
import com.gitee.wsl.model.process.response.ProcessResponse
import com.gitee.wsl.model.process.response.complete
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.Job

/**
 * Prepared statement for a HTTP client request.
 * This statement doesn't perform any network requests until [execute] method call.
 * [Statement] is safe to execute multiple times.
 *
 * Example: [Streaming data](https://ktor.io/docs/response.html#streaming)
 */
 class Statement<Model:ProcessModel>(
    private val builder: ProcessRequestBuilder,
    @PublishedApi
    internal val client: Model
) {

    /**
     * Executes this statement and calls the [block] with the streaming [response].
     *
     * The [response] argument holds a network connection until the [block] isn't completed. You can read the body
     * on-demand or at once with [body<T>()] method.
     *
     * After [block] finishes, [response] will be completed body will be discarded or released depends on the engine configuration.
     *
     * Please note: the [response] instance will be canceled and shouldn't be passed outside of [block].
     */
     suspend fun <T> execute(block: suspend (processResponse: ProcessResponse) -> T): T = unwrapRequestTimeoutException {
        val response = fetchStreamingResponse()

        try {
            return block(response)
        } finally {
            response.cleanup()
        }
    }

    /**
     * Executes this statement and download the response.
     * After the method execution finishes, the client downloads the response body in memory and release the connection.
     *
     * To receive exact type, consider using [body<T>()] method.
     */
     suspend fun execute(): ProcessResponse = fetchResponse()

    /**
     * Executes this statement and runs [HttpClient.responsePipeline] with the response and expected type [T].
     *
     * Note if T is a streaming type, you should manage how to close it manually.
     */

    internal suspend inline fun <reified T> body(): T = unwrapRequestTimeoutException {
        val response = fetchStreamingResponse()
        return try {
            response.body()
        } finally {
            response.complete()
        }
    }

    /**
     * Executes this statement and runs the [block] with a [HttpClient.responsePipeline] execution result.
     *
     * Note that T can be a streamed type such as [ByteReadChannel].
     */
    internal suspend inline fun <reified T, R> body(
        crossinline block: suspend (response: T) -> R
    ): R = unwrapRequestTimeoutException {
        val processResponse: ProcessResponse = fetchStreamingResponse()
        try {
            val result = processResponse.body<T>()
            return block(result)
        } finally {
            processResponse.cleanup()
        }
    }

    /**
     * Returns [ProcessResponse] with open streaming body.
     */
    @PublishedApi
    internal suspend fun fetchStreamingResponse(): ProcessResponse = unwrapRequestTimeoutException {
        val builder = ProcessRequestBuilder().takeFromWithExecutionContext(builder)
        //builder.skipSavingBody()

        val call = client.execute(builder)
        return call.processResponse
    }

    /**
     * Returns [ProcessResponse] with saved body.
     */
    @PublishedApi
    internal suspend fun fetchResponse(): ProcessResponse = unwrapRequestTimeoutException {
        val builder = ProcessRequestBuilder().takeFromWithExecutionContext(builder)
        val call = client.execute(builder)
        val result = call.processResponse
        call.processResponse.cleanup()

        return result
    }

    /**
     * Completes [ProcessResponse] and releases resources.
     */
    @PublishedApi
    internal suspend fun ProcessResponse.cleanup() {
        val job = coroutineContext[Job]!! as CompletableJob

        job.apply {
            complete()
            try {
                //rawContent.cancel()
            } catch (_: Throwable) {
            }
            join()
        }
    }

    override fun toString(): String = "HttpStatement[${builder}]"
}

internal inline fun <T> unwrapRequestTimeoutException(block: () -> T): T {
    try {
        return block()
    } catch (cause: CancellationException) {
        throw cause//.unwrapCancellationException()
    }
}